﻿// (c) Copyright 2013 Mike Riedel.
// This source is subject to the Microsoft Public License (Ms-PL).
// All other rights reserved.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace FreshMeat.Threading
{
   /// <summary>
   /// Regulates access to a resource by multiple threads.
   /// </summary>
   public class Lock : ILock
   {
      readonly object exclusive = new object();
      readonly LockContext context;

      /// <summary>
      /// Creates default instance.
      /// </summary>
      public Lock()
      {
         context = new LockContext(Done);
      }

      /// <summary>
      /// Waits until the protected resource is available for access by the calling thread.
      /// </summary>
      public void Wait()
      {
         Monitor.Enter(exclusive);
      }

      /// <summary>
      /// Waits until the protected resource is available for access by the calling thread.
      /// </summary>
      /// <returns></returns>
      public Task WaitAsync()
      {
         // If we can get the lock instantly then return an already-completed task.
         if (Monitor.TryEnter(exclusive))
            return TaskEx.FromResult<object>(null);

         // Otherwise, return a task that waits for the lock.
         return TaskEx.Run(() => Monitor.TryEnter(exclusive));
      }

      /// <summary>
      /// Indicates the calling threading is done accessing the resource.
      /// </summary>
      public void Done()
      {
         Monitor.Exit(exclusive);
      }

      /// <summary>
      /// Performs a <see cref="ILock.Wait"/> and then returns a <see cref="IDisposable"/>
      /// object that will call <see cref="ILock.Done"/> when it is disposed.
      /// The typical usage pattern is to put this into a <code>using</code> block containing
      /// the code which read from the protected resource; this avoid the the common usage
      /// of forgetting to call <see cref="ILock.Done"/>.
      /// </summary>
      /// <returns>Object which must be disposed.</returns>
      public IDisposable Context()
      {
         Wait();
         return context;
      }
   }
}